<?php

1->负载均衡
   -) '核心概念'
       . 雨露均沾
       . 不能让某个节点特别累
       . 有难大家一起抗

   -) '多思考'
       当用户量为几百几千时，单体应用就足够了。
       当用户量上万时，就要看服务撑不撑得住。
       在设计系统时，要留有余量，目标1W用户的系统，余量最好达2w，并且要时常思考如果用户10倍10倍的增长，架构应该怎样演进。


2->负载均衡策略
   -) '客户端'
       (机器列表)                             |------->ServiceA
       客户端 ------------- LB策略 ------------|------->ServiceB
                                             |------->ServiceC
       1. 客户端维护服务注册的机器列表
       2. 机器列表可以'动态更新'

   -) '服务端'                                                       |------->ServiceA
       客户端 -------------- 软件LB(Nginx) / 硬件LB(F5) --------------|------->ServiceB
                                                                    |------->ServiceC
   -) '模式比较'
       大型应用中, 一般由 [客户端 + 服务端] 两种负载均衡搭配使用
       - '客户端'
       . 开发团队灵活修改
       . 运维成本低
       . 强依赖注册中心
       . 微服务框架

       - '服务端'
       . NetOpts把控修改
       . 运维成本高
       . 通常不依赖注册中心
       . tomcat、jboss部署传统应用


3->Ribbon体系架构解析
   (Ribbon可以'不依赖微服务存在', 可以'单独集成'到项目中作为'负载均衡'使用.)
   -) '优点'
       . Ribbon的体系架构相对简单，职责也比较单一，只应用于负载均衡;
       . 整套负载均衡由'7个具体策略'组成，不管你是什么特殊需求，都有合适的策略供你选择;
       . 适配性好，跟谁都能搭配，SpringCloud里的五小强（eureka，feign，gateway，zuul，hystrix），谁拿都能用。

   (一个http请求先被转发到eureka上，此时eureka仍然通过服务发现获取了'所有服务节点的物理地址'，但微提示他不知道该调用哪一个，把请求转到ribbon手中...)
   -) '流程'
       IPing: IPing是Ribbon的一套'healthcheck'机制，顾名思义就是要'Ping一下目标机器是否在线'，一般情况下IPing并不会主动向服务节点发起healthcheck请求，
              Ribbon后台通过'静默处理返回true'默认表示所有节点都处于存活状态。(和Eureka集成的时候会检查服务节点的'UP状态').

       IRule: 这就是Ribbon的组件库了，各种负载均衡策略'都继承自IRule接口'。所有经过Ribbon的请求都会先请示IRule一把，
              找到负载均衡策略选定的目标机器，然后再把请求转发过去。


4->使用Ribbon简化Eureka的服务间调用
   前景回顾: 在eureka上面注册的多个微服务之间的通信, 通过引入'LoadBalancerClient + RestTemplate - 手动拼接'
           要请求的'目标的 [IP+Port]'来获取对应的服务 >-<

   - 'pom'
      <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
      </dependency>

   - '注解'
      @Bean
      @LoadBalanced
      public RestTemplate restTemplate(RestTemplateBuilder builder) {
          return builder.build();
      }

   - '代码'
      @GetMapping("/sayHelloByRibbon")
      public String sayHelloByRibbon() {
          return template.getForObject("http://eureka-client/sayHello", String.class);
      }
   优点: 不需要'手动拼接 [IP + Port]', 只需要'服务名/接口'即可调用!


5->Ribbon加载模式 (默认懒加载)
   - '开启懒加载'
      ribbon.eager-load.enabled=true               # 开启了Ribbon的饥饿加载模式
      ribbon.eager-load.clients=ribbon-consumer    # 指定了需要应用饥饿加载的服务名称

      这样LoadBalancer初始化日志在方法调用之前就打印出来了。

   - '思考'
      大家在实际开发中，一定要秉承“机器是不会出错的”这个观点，所有所谓的环境问题，或者偶然不可重现的问题，必定都有一个导致问题的根本原因。
      就像有很多超自然现象无法用现有的人类科学解释，如果你的技术积累没有到一定程度，这类“环境问题”对自己来说就是一个玄学。
      大家以后工作中肯定还会遇到很多这类问题，不要做“懒加载”，我们要勤快的刨根问底把问题解决掉，解决别人无法解决的问题，这就是使你从团队中脱颖而出的有效途径。


6->Ribbon负载均衡模式
   6.1->RandomRule (随性而为.周一)
        #  收心归寂灭，随性过光阴
        #                    ——宋代·文天祥《偶赋》
        '随机'访问可用节点.


   6.2->RoundRobinRule (按部就班.周二 - 默认)
        #  蜗牛背着那重重的壳呀，一步一步地往上爬
        '轮询'访问可用节点.
        假如在多线程环境下，两个请求同时访问这个Rule是否会读取到相同节点呢？
        不会，这靠的是RandomRobinRule底层的自旋锁+CAS的同步操作。CAS的全称是compare and swap，是一种借助操作系统函数来实现的同步操作。
        比如Eureka为了防止服务下线被重复调用，就使用AtomicBoolean的CAS方法做同步控制，CAS+自旋锁这套组合技是高并发下最廉价的线程安全手段，
        因为这套操作不需要锁定系统资源。当然了，有优点必然也有缺点，自旋锁如果迟迟不能释放，将会带来CPU资源的浪费，因为自旋本身并不会执行任何业务逻辑，
        而是单纯的使CPU“空转”。所以通常情况下会对自旋锁的旋转次数做一个限制，比如JDK中synchronize底层的锁升级策略，就对自旋次数做了动态调整。
        // CAS+自旋锁获取系统资源的打开方式，真实应用中还要注意防止无休止自旋：
        // 或者for (;;) 做自旋
        while (true) {
        	  // cas操作
      	 if (cas(expected, update)) {
      		  // 业务逻辑代码
      		  // break或退出return
      	  }
        }


   6.3->RetryRule (卷土重来.周三)
        #  江东子弟多才俊，卷土重来未可知
        #										        --唐代·杜牧《题乌江亭》
        RetryRule: 是一个类似装饰器模式的Rule, 他的BUFF就是给其他负载均衡策略加上'重试'功能。而在RetryRule里还藏着一个subRule，
        这才是隐藏在下面的真正被执行的负载均衡策略，RetryRule正是要为它添加重试功能（如果初始化时没指定subRule，将默认使用'RoundRobinRule'）重试


   6.4->WeightedResponseTimeRule (能者多劳.周四)
        #  巧者劳而知者忧，无能者无所求
        #		           					 --战国·庄子《列御寇》
        这个Rule继承自RoundRobinRule，他会根据服务节点的响应时间'计算权重'，响应时间越长权重就越低，响应越快则权重越高，
        '权重的高低决定了机器被选中概率的高低'。也就是说，响应时间越小的机器，被选中的概率越大。
        由于服务器刚启动的时候，对各个服务节点采样不足，因此会采用轮询策略，当积累到一定的样本时候，会切换到WeightedResponseTimeRule模式。
        . 计算权重
        . 权重越高, 相映时间越小, 被选中的概率越大


   6.5->BastAvailableRule (让最闲的人来.周五)
        # 莫等闲，白了少年头，空悲切
        # 										   --南宋·岳飞《满江红·写怀》
        应该说这个Rule有点智能的味道了，在'过滤掉故障服务'以后，它会基于'过去30分钟的统计结果'选取当前并发量最小的服务节点，
        也就是最“闲”的节点作为目标地址。如果统计结果尚未生成，则采用轮询的方式选定节点。
        . 过滤掉故障服务
        . 选取当前并发量最小的服务节点


   6.6->AvailabilityFilteringRule (我是有底线的.周六)
        这个规则底层依赖RandomRobinRule来选取节点，但并非来者不拒，它也是有一些底线的，必须要满足它的最低要求的节点才会被选中。
        如果节点满足了要求，无论其响应时间或者当前并发量是什么，都会被选中。
        . 满足最低要求

        每次AvailabilityFilteringRule（简称AFR）都会请求RobinRule挑选一个节点，然后对这个节点做以下两步检查:
        . 是否处于熔断状态（熔断是Hystrix中的知识点，后面章节会讲到，这里大家可以把熔断当做服务不可用）
        . 节点当前的active请求连接数超过阈值，超过了则表示节点目前太忙，不适合接客

        注意: 如果被选中的server不幸挂掉了检查，那么AFR会自动重试（'次数最多10次'），让RobinRule重新选择一个服务节点。
        if (count >= 10) {
            log.warn("No available alive servers after 10 tries from load balancer: " + lb);
        }


   6.7->ZoneAvoidanceRule (我的地盘我作主.周日)
        这个过滤器包含了组合过滤条件，分别是Zone级别和可用性级别。
        Zone Filter: 在Eureka注册中一个服务节点有Zone, Region和URL三个身份信息，其中Zone可以理解为机房大区（未指定则由Eureka给定默认值），
                     而这里会对这个Zone的健康情况过滤其下面所有服务节点。

        可用性过滤: 这里和AvailabilityFilteringRule的验证非常像，会'过滤掉当前并发量较大，或者处于熔断状态的服务节点'。

   注意: 以上7种模式都'直接或间接'继承了'AbstractLoadBalancerRule'抽象类.


7->指定Ribbon负载模式 (默认轮询 - RoundRobinRule)
  -) '全局配置类'
      将需要指定的策略, 作为'配置类'注册.
      @Configuration
      public class RibbonLBConfig {
          @Bean
          public IRule defaultLBStrategy() {
               return new RandomRule();
          }
      }

  -) '针对服务'
      . '配置文件'
         # 服务名.                                         Bibbon的策略[类路径]
         eureka-client.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule

      . '注解'
         @Configuration
         # name => 服务名  configuration => 需要加载的[策略类路径]
         @RibbonClient(name = "eureka-client", configuration = {com.netflix.loadbalancer.RandomRule.class})
         public class RibbonLBConfig {
         }
      注意: 上面策略配置, 是指'请求对应的服务', 比如: 请求'eureka-client 集群', 选择的策略是'com.netflix.loadbalancer.RandomRule.class'.

  -) '优先级'
       单服务 > 全局
             |
      (注解 > 配置文件)
      注意: 关于配置文件的'加载顺序'很重要! 配置文件加载顺序不同很可能会'覆盖原有配置'.


8->负载均衡策略源码 (IRule)
   . RandomRule ('伪随机数', 线程出让, 一直重试)
   . RoundRobinRule ('自旋锁', 重试10次, 动态取值: (currentCount + 1) % count, 顺序排列的节点全部宕机重启的话, 压力全部堆积到'Service-1'!!!)
   . BaseAvailableRule (查询各服务的负载量)
   . RetryRule (线程Task任务,系统给定'重试时间')


9->LoadBalancer注解
   负载均衡器LoadBalancer的原理
   @LoadBalanced这个注解会将RestTemplate传送到Ribbon的自动装配类里面进行改造
   @LoadBalanced这个注解一头挂在RestTemplate上，另一个挂在LBAutoConfig这个类增加各种拦截器，对网络请求进行拦截


10->IPing工作模式
    DummyPing: 自娱自乐式, (直接返回'true')
    NIWSDiscoveryPing: 借助'eureka服务注册'查看各服务是否'UP', (一般默认采用这一种, 但是时效性不是很快, 因为eureka向各个微服务检测也是需要每隔一段时间才检测的...)
    PingUrl: 主动出击式. (主动去'ping'请求端, 但是会: 额外增加后台服务器压力)


11->自定义IRule
   -) '一致性Hash'
       简单来说就是:
       将服务节点'(均匀)分布'在'圆环上 - INT范围', 然后根据客户端请求定义'特征值', 并将此特征值'Hash + MD5'落在'圆环某点',
       然后该圆环'顺时针/逆时针'进行服务器节点选择, 请求落在'最近的服务节点上', 这样即使其中有'节点宕机', 也不会影响正常访问.
       (思考: 每个节点宕机后, 压力都转移到'下一个节点', 怎么预防下一个节点不会因为'堆积超负荷'? (检测可用节点数量, 设置'节点阈值'并做预警处理...))

   -) '实现 - (详见.附录1-自定义hash一致性负载均衡策略.java)'
